/*
 *		Este es el main del simulador quirurgico
 */


#if defined (_WIN32)
#include <windows.h>
#include "IncludePolhemus/PDI.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string>
#include <gtk/gtk.h>
#include <gtk/gtkgl.h>
#include <gdk/gdkkeysyms.h>

#include "Sesion.h"
#include "Enumeraciones.h"

#define DEFAULT_WIDTH  600
#define DEFAULT_HEIGHT 600
#define DEFAULT_TITLE  "Simulador Quirurgico EAFIT-CES"

#include <GL/gl.h>
#include <GL/glu.h>

static GdkGLConfig *configure_gl      (void);
static GtkWidget   *create_window     (GdkGLConfig *glconfig);
static void         idle_add          (GtkWidget   *widget);
static void         idle_remove       (GtkWidget   *widget);

static gboolean animate = TRUE;

//Objeto que maneja las sesiones
Sesion objSesion;

float xtraslMain, ytraslMain, ztraslMain, xrotMain, yrotMain, zrotMain;
float xluz, yluz, zluz;

//Manipulacion modelo fisico
double dblValorTemp1, dblValorTemp2, dblValorTemp3;

//Valores iniciales nodo 50
double dblNodoX, dblNodoY, dblNodoZ;

//Ver Volumenes Limite Objetos Simulacion 
bool blnVolumenesLimite = false;

//Id de los triangulos y el vertice que colisiona con el instrumento 1 y 2 con el organo
int intIDTrianguloColisionInst1;
int intIDVerticeColisionInst1;

int intIDTrianguloColisionInst2;
int intIDVerticeColisionInst2;


//Variables de visualizacion
GLfloat angle, fAspect;
int frame,timet,timebase=0;
char s[30];


//Toda la inicializacion de OpenGL debe ser realizada aqui
static void realize (GtkWidget *widget, gpointer   data)
{
	GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
	GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

	//OpenGL Begin
	if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
		return;

	objSesion.set_TipoSesion1(TRANSPORTAR_CUBOS);
	objSesion.set_NroCubos(3);
	objSesion.InicializarSesiones();

	xtraslMain = 0.0f;
	ytraslMain = 0.0f;
	ztraslMain = 0.0f;

	xrotMain = 0.0f;
	yrotMain = 0.0f;
	zrotMain = 0.0f;

	xluz = 0.0f;
	yluz = 0.0f;
	zluz = 0.0f;
	
	gdk_gl_drawable_gl_end (gldrawable);
	//OpenGL end

  return;
}


//Cualquier procesamiento requerido cuando el area de dibujo de openGL es
//reconfigurada debe ser realizada aqui. Casi siempre sera usado para el resize
//del viewport de openGL cuando la ventana es cambiada de tamano
static gboolean configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
	GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
	GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

	GLfloat w = widget->allocation.width;
	GLfloat h = widget->allocation.height;

	//OpenGL Begin
	if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
		return FALSE;

	if (h==0)		// Previene una division por cero
	{
		h=1;		// Haciendolo igual a 1
	}

	glViewport(0,0,w,h);// Resetea el viewport actual

	glMatrixMode(GL_PROJECTION);// Selecciona la matriz de proyeccion
	glLoadIdentity();		// Reinicia la matriz de proyeccion

	gluPerspective(45.0f,(GLfloat)w/(GLfloat)h,0.1f,5000.0f);

	glMatrixMode(GL_MODELVIEW);							glLoadIdentity();		

	gdk_gl_drawable_gl_end (gldrawable);
	//OpenGL end

  return TRUE;
}

//Todo lo que openGL tenga que redibujar tiene que estar aqui. Esta es
//repetidamente llamada como la rutina de redibujo 
static gboolean expose_event (GtkWidget *widget, GdkEventExpose *event,gpointer data)
{
	GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
	GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

	//OpenGL Begin
	if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
	return FALSE;


	objSesion.DibujarSesion();

	//Swap buffers
	if (gdk_gl_drawable_is_double_buffered (gldrawable))
	gdk_gl_drawable_swap_buffers (gldrawable);
	else
	glFlush ();

	gdk_gl_drawable_gl_end (gldrawable);
	//OpenGL end

	return TRUE;
}

void HayColision()
{
	
}

static gboolean idle (GtkWidget *widget)
{
	objSesion.ActualizarSesion();
	
	//Determina si hay colision
	HayColision();

	/* Invalidate the whole window. */
	gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);

	/* Update synchronously. */
	gdk_window_process_updates (widget->window, FALSE);

  return TRUE;
}

//Cualquier procesamiento requerido cuando la capacidad del la ventana
//no lograda debe ser realizada aqui
static void unrealize (GtkWidget *widget, gpointer   data)
{
  //Llene aca con lo que necesite
}

//Funciones auxiliares para adicionar y remover funciones idle

static guint idle_id = 0;

static void idle_add (GtkWidget *widget)
{
	if (idle_id == 0)
	{
		idle_id = gtk_idle_add_priority (GDK_PRIORITY_REDRAW,
						(GtkFunction) idle,
						widget);
	}
}

static void idle_remove (GtkWidget *widget)
{
	if (idle_id != 0)
	{
		gtk_idle_remove (idle_id);
		idle_id = 0;
	}
}

//Cualquier procesamiento requerido cuando el area de dibujo de open-GL
//trazada debe ser realizada aqui
static gboolean map_event (GtkWidget *widget, GdkEvent  *event, gpointer  data)
{
	if (animate)
		idle_add (widget);

	return TRUE;
}

//Cualquier procesamiento requerido cuando el area de dibujo de openGL
//es destrazado debe ser realizado aqui
static gboolean unmap_event (GtkWidget *widget, GdkEvent *event, gpointer  data)
{
	idle_remove (widget);

	return TRUE;
}

//Cualquier procesamiento requerido cuando el area de dibujo de OpenGL es visualmente
//oscuro debe ser realizado aca
static gboolean visibility_notify_event (GtkWidget *widget,GdkEventVisibility *event, gpointer data)
{
	if (animate)
	{
		if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
			idle_remove (widget);
		else
			idle_add (widget);
	}

  return TRUE;
}

//Cualquier procesamiento requerido cuando se presione una tecla ocurrira aca
static gboolean key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	switch (event->keyval)
	{
		//Traslacion Higado
		case GDK_Up:
			ztraslMain += 5.0f;
			objSesion.set_TraslacionZ(ztraslMain);
		    break;

		case GDK_Down:
			ztraslMain -= 5.0;
			objSesion.set_TraslacionZ(ztraslMain);
			break;

		case GDK_Left:
		    xtraslMain += 5.0f;
			objSesion.set_TraslacionX(xtraslMain);
			break;

		case GDK_Right:
			xtraslMain -= 5.0f;
			objSesion.set_TraslacionX(xtraslMain);
			break;

		case GDK_Home:
			ytraslMain -= 5.0f;
			objSesion.set_TraslacionY(ytraslMain);
			break;

		case GDK_End:
			ytraslMain += 5.0f;
			objSesion.set_TraslacionY(ytraslMain);
			break;

		case GDK_Escape:
			gtk_main_quit ();
			break;

		//Rotacion Higado
		case GDK_F1:
			xrotMain -= 3.0f;
			objSesion.set_RotacionX(xrotMain);
			break;

		case GDK_F2:
			xrotMain += 3.0f;
			objSesion.set_RotacionX(xrotMain);
			break;

		case GDK_F3:
			yrotMain -= 3.0f;
			objSesion.set_RotacionY(yrotMain);
			break;

		case GDK_F4:
			yrotMain += 3.0f;
			objSesion.set_RotacionY(yrotMain);
			break;

		case GDK_F5:
			zrotMain += 3.0f;
			objSesion.set_RotacionZ(zrotMain);
			break;

		case GDK_F6:
			zrotMain -= 3.0f;
			objSesion.set_RotacionZ(zrotMain);
			break;

		case GDK_Page_Up:
			xluz += 10.0f;
			objSesion.setLuzPosX(xluz);
			break;
		
		case GDK_Page_Down:
			xluz -= 10.0f;
			objSesion.setLuzPosX(xluz);
			break;

		case GDK_Insert:
			yluz += 10.0f;
			objSesion.setLuzPosY(yluz);
			break;

		case GDK_Delete:
			yluz -= 10.0f;
			objSesion.setLuzPosY(yluz);
			break;

		case GDK_1:
			zluz  += 10.0f;
			objSesion.setLuzPosZ(zluz);
			break;
		
		case GDK_2:
			zluz -= 10.0f;
			objSesion.setLuzPosZ(zluz);
			break;

	default:
		return FALSE;
	}

	//cout << xtraslMain << "  " << ytraslMain << "  " << ztraslMain << endl;
	
	return TRUE;
}


static void menuitem_response( char *String )
{
	string tmp(String);

	//Menu Visualizacion
	if (tmp.compare("Puntos") == 0)
	{
		objSesion.get_Visualizacion().set_TipoVisualizacion(DIBUJAR_PUNTOS);
	}
	else if(tmp.compare("Wireframe") == 0)
	{
		objSesion.get_Visualizacion().set_TipoVisualizacion(DIBUJAR_LINEAS);
	}
	else if(tmp.compare("Flat") == 0)
	{
		objSesion.get_Visualizacion().set_TipoVisualizacion(DIBUJAR_CONTORNO_FLAT);
	}
	else if(tmp.compare("Smooth") == 0)
	{
		objSesion.get_Visualizacion().set_TipoVisualizacion(DIBUJAR_CONTORNO_SMOOTH);
	}

	//Menu Sesion
	if(tmp.compare("Iniciar Evaluacion") == 0)
	{
		objSesion.IniciarEvaluacion();
	}
	else if(tmp.compare("Finalizar Evaluacion") == 0)
	{
		objSesion.FinalizarEvaluacion();
	}
	else if(tmp.compare("Sesion 1: Transportar Cubos") == 0)
	{
		objSesion.set_TipoSesion1(TRANSPORTAR_CUBOS);
		objSesion.set_NroCubos(3);
		objSesion.InicializarSesiones();

		xtraslMain = 0.0f;
		ytraslMain = 0.0f;
		ztraslMain = 0.0f;

		xrotMain = 0.0f;
		yrotMain = 0.0f;
		zrotMain = 0.0f;
	}
	else if(tmp.compare("Sesion 2: Ordenar Cubos") == 0)
	{
		objSesion.set_TipoSesion1(ORDENAR_CUBOS);
		objSesion.set_NroCubos(3);
		objSesion.InicializarSesiones();

		xtraslMain = 0.0f;
		ytraslMain = 0.0f;
		ztraslMain = 0.0f;

		xrotMain = 0.0f;
		yrotMain = 0.0f;
		zrotMain = 0.0f;
	}
	else if(tmp.compare("Sesion 3: Simulacion Higado") == 0)
	{
		objSesion.set_TipoSesion1(SIMULACION_HIGADO);
		objSesion.InicializarSesiones();

		xtraslMain = 0.0f;
		ytraslMain = 0.0f;
		ztraslMain = 0.0f;

		xrotMain = 0.0f;
		yrotMain = 0.0f;
		zrotMain = 0.0f;
	}

	//Menu Ver
	if(tmp.compare("Volumenes Limite") == 0)
	{
		if(blnVolumenesLimite == false)
		{
			blnVolumenesLimite = true;
		}
		else
		{
			blnVolumenesLimite = false;
		}
		objSesion.set_VolumenLimite(blnVolumenesLimite);
	}
}


static GtkWidget *create_window (GdkGLConfig *glconfig)
{
	GtkWidget *window;
	GtkWidget *vbox;
	GtkWidget *drawing_area;
	GtkWidget *menu1;
	GtkWidget *menu2;
	GtkWidget *menu3;
	GtkWidget *menu_bar;
	GtkWidget *root_menu1;
	GtkWidget *root_menu2;
	GtkWidget *root_menu3;
	GtkWidget *menu_items1;
	GtkWidget *menu_items2;
	GtkWidget *menu_items3;
	char buf1[128];
	char buf2[128];
	char buf3[128];

	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title (GTK_WINDOW (window), DEFAULT_TITLE);

	// Redibuja automaticamente si cualquiera de sus hijos
	//cambia de localizacion
	gtk_container_set_reallocate_redraws (GTK_CONTAINER (window), TRUE);

	// Conecta las senales handlers a la ventana
	g_signal_connect (G_OBJECT (window), "delete_event",G_CALLBACK (gtk_main_quit), NULL);

	//VBox.
	vbox = gtk_vbox_new (FALSE, 0);
	gtk_container_add (GTK_CONTAINER (window), vbox);
	gtk_widget_show (vbox);

	//Dibujando el area para dibujar la escena OpenGL Drawing area to draw OpenGL scene.
	drawing_area = gtk_drawing_area_new ();
	gtk_widget_set_size_request (drawing_area, DEFAULT_WIDTH, DEFAULT_HEIGHT);

	// Le pone la capacidad de OpenGL Set OpenGL-capability to the widget
	gtk_widget_set_gl_capability (drawing_area, glconfig, NULL,TRUE, GDK_GL_RGBA_TYPE);

	gtk_widget_add_events (drawing_area,
				GDK_BUTTON1_MOTION_MASK    |
				GDK_BUTTON2_MOTION_MASK    |
				GDK_BUTTON_PRESS_MASK      |
				GDK_VISIBILITY_NOTIFY_MASK);

	//Conecta las senales manejadoras a el area de dibujo
	g_signal_connect_after (G_OBJECT (drawing_area), "realize", G_CALLBACK (realize), NULL);
	g_signal_connect (G_OBJECT (drawing_area), "configure_event", G_CALLBACK (configure_event), NULL);
	g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL);
	g_signal_connect (G_OBJECT (drawing_area), "unrealize", G_CALLBACK (unrealize), NULL);

	/// Manejador de los enventos que presionan las teclase
	g_signal_connect_swapped (G_OBJECT (window), "key_press_event", G_CALLBACK (key_press_event), drawing_area);

	//Para la funcion idle
	g_signal_connect (G_OBJECT (drawing_area), "map_event", G_CALLBACK (map_event), NULL);
	g_signal_connect (G_OBJECT (drawing_area), "unmap_event", G_CALLBACK (unmap_event), NULL);
	g_signal_connect (G_OBJECT (drawing_area), "visibility_notify_event", G_CALLBACK (visibility_notify_event), NULL);

	gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
	gtk_widget_show (drawing_area);

	//Creando el menu
	menu1 = gtk_menu_new ();

	//Menu Sesion

	sprintf(buf1,"Iniciar Evaluacion");
	menu_items1 = gtk_menu_item_new_with_label(buf1);
	gtk_menu_append (GTK_MENU (menu1), menu_items1);
	gtk_signal_connect_object (GTK_OBJECT (menu_items1), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf1));
	gtk_widget_show (menu_items1);

	sprintf(buf1,"Finalizar Evaluacion");
	menu_items1 = gtk_menu_item_new_with_label(buf1);
	gtk_menu_append (GTK_MENU (menu1), menu_items1);
	gtk_signal_connect_object (GTK_OBJECT (menu_items1), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf1));
	gtk_widget_show (menu_items1);

	sprintf(buf1,"Sesion 1: Transportar Cubos");
	menu_items1 = gtk_menu_item_new_with_label(buf1);
	gtk_menu_append (GTK_MENU (menu1), menu_items1);
	gtk_signal_connect_object (GTK_OBJECT (menu_items1), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf1));
	gtk_widget_show (menu_items1);

	sprintf(buf1,"Sesion 2: Ordenar Cubos");
	menu_items1 = gtk_menu_item_new_with_label(buf1);
	gtk_menu_append (GTK_MENU (menu1), menu_items1);
	gtk_signal_connect_object (GTK_OBJECT (menu_items1), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf1));
	gtk_widget_show (menu_items1);
	
	sprintf(buf1,"Sesion 3: Simulacion Higado");
	menu_items1 = gtk_menu_item_new_with_label(buf1);
	gtk_menu_append (GTK_MENU (menu1), menu_items1);
	gtk_signal_connect_object (GTK_OBJECT (menu_items1), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf1));
	gtk_widget_show (menu_items1);

	root_menu1 = gtk_menu_item_new_with_label ("Sesion");

    gtk_widget_show (root_menu1);

	gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu1), menu1);

	//Menu Visualizacion

	menu2 = gtk_menu_new ();

	sprintf(buf2,"Puntos");
	menu_items2 = gtk_menu_item_new_with_label(buf2);
	gtk_menu_append (GTK_MENU (menu2), menu_items2);
	gtk_signal_connect_object (GTK_OBJECT (menu_items2), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf2));
	gtk_widget_show (menu_items2);

	sprintf(buf2,"Wireframe");
	menu_items2 = gtk_menu_item_new_with_label(buf2);
	gtk_menu_append (GTK_MENU (menu2), menu_items2);
	gtk_signal_connect_object (GTK_OBJECT (menu_items2), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf2));
	gtk_widget_show (menu_items2);
	
	sprintf(buf2,"Flat");
	menu_items2 = gtk_menu_item_new_with_label(buf2);
	gtk_menu_append (GTK_MENU (menu2), menu_items2);
	gtk_signal_connect_object (GTK_OBJECT (menu_items2), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf2));
	gtk_widget_show (menu_items2);
	
	sprintf(buf2,"Smooth");
	menu_items2 = gtk_menu_item_new_with_label(buf2);
	gtk_menu_append (GTK_MENU (menu2), menu_items2);
	gtk_signal_connect_object (GTK_OBJECT (menu_items2), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf2));
	gtk_widget_show (menu_items2);

	root_menu2 = gtk_menu_item_new_with_label ("Visualizacion");

    gtk_widget_show (root_menu2);

	gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu2), menu2);

	//Menu Ver

	menu3 = gtk_menu_new ();

	sprintf(buf3,"Volumenes Limite");
	menu_items3 = gtk_menu_item_new_with_label(buf3);
	gtk_menu_append (GTK_MENU (menu3), menu_items3);
	gtk_signal_connect_object (GTK_OBJECT (menu_items3), "activate",
			GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf3));
	gtk_widget_show (menu_items3);

	root_menu3 = gtk_menu_item_new_with_label ("Ver");

    gtk_widget_show (root_menu3);

	gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu3), menu3);

	//Menu bar
    menu_bar = gtk_menu_bar_new ();
    gtk_box_pack_start (GTK_BOX (vbox), menu_bar, FALSE, FALSE, 2);
    gtk_widget_show (menu_bar);

    gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu1);
	gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu2);
	gtk_menu_bar_append(GTK_MENU_BAR (menu_bar),root_menu3);


	return window;
}


static GdkGLConfig * configure_gl (void)
{
	GdkGLConfig *glconfig;

	//Intenta doble buffer visual
	glconfig = gdk_gl_config_new_by_mode ( GdkGLConfigMode(
					int(GDK_GL_MODE_RGB)|
					int(GDK_GL_MODE_DEPTH)|
					int(GDK_GL_MODE_DOUBLE) ) );

	if (glconfig == NULL)
	{
		g_print ("\n*** No puede realizar doble buffer visual.\n");
		g_print ("\n*** Intentando buffer simple.\n");

		//Intenta buffer unico visual
		glconfig = gdk_gl_config_new_by_mode (GdkGLConfigMode(
					int(GDK_GL_MODE_RGB)|
					int(GDK_GL_MODE_DEPTH)));
		if (glconfig == NULL)
		{
			g_print ("*** No se encontraron apropiadas capacidades visuales OpenGL.\n");
			exit (1);
		}
	}

	return glconfig;
}

int main (int argc, char *argv[])
{
	GtkWidget *window;
	GdkGLConfig *glconfig;

	//Inicializa GTK
	gtk_init (&argc, &argv);

	//Inicializa GtkGLExt
	gtk_gl_init (&argc, &argv);

	//Configura el buffer de OpenGL
	glconfig = configure_gl ();

	window = create_window (glconfig);

	gtk_widget_show (window);

	gtk_main ();

	return 0;
}